<template>
	<div>拖拽特效</div>
	<div>移动端的拖拽效果,使用touch事件 + requestAnimationFrame 进行实现</div>
	<div class="draggable" ref="myRef" @click="dragClick">自由移动</div>
	<div class="draggable2" ref="myRef2" @click="dragClick">左右两侧</div>
</template>

<script>
	import {
		ref,
		reactive,
		onMounted
	} from 'vue'


	export default {
		//Vue3获取Dom元素
		//1.dom上写上ref 2setup函数内部写上ref 3retrun 出来（重点）
		setup() {
			const myRef = ref(null);
			const myRef2 = ref(null);
			const arr = ref([1, 2, 3]);

			onMounted(() => {
				console.log(myRef, myRef.value);
				var details = myRef.value
				console.dir(details) //console.dir()可以显示一个对象所有的属性和方法
			});
			return {
				myRef,
				myRef2,
			}
		},
		data() {
			return {}
		},
		mounted() {
			this.nextTickTest();
			this.nextTickTest2();
		},
		methods: {
			//drag
			dragClick() {
				console.log('this.dragClick=')
			},

			nextTickTest() {
				console.log('this.myRef=', this.myRef)
				this.draggable(this.myRef)
			},
			nextTickTest2() {
				console.log('this.myRef=', this.myRef2)
				this.draggable(this.myRef2, false)
			},

			/**
			 * 封装拖拽函数
			 * @param $ele 需要拖拽的元素
			 * @param adsorb { x = 20, y = 60 } 上下左右吸附的边距
			 */
			draggable($ele, center = true, adsorb = {
				x: 20,
				y: 80
			}) {
				if (!$ele) {
					throw new Error("必须是可拖拽元素");
				}
				//提高性能，代替setTimeout，做帧动画效果
				const raf = window.requestAnimationFrame;
				const isAndroid = / Android /.test(navigator.userAgent)
				// 开始时候的位置
				let startX = 0;
				let startY = 0;

				// 移动过程中的 left 和 top，其实通过这俩参数，就能确定元素位置
				let left = 0;
				let top = 0;

				// 屏幕的宽高
				const cw = document.documentElement.clientWidth;
				const ch = document.documentElement.clientHeight;

				// 获取到元素自身的宽高
				const {
					width,
					height
				} = $ele.getBoundingClientRect();

				/**
				 * 开始拖拽的事件
				 */
				$ele.addEventListener(
					"touchstart",
					function(event) {
						startX = event.targetTouches[0].pageX;
						startY = event.targetTouches[0].pageY;

						top = $ele.offsetTop;
						left = $ele.offsetLeft;

						event.preventDefault();
					},
					false
				);

				/**
				 * 拖拽过程中的事件
				 */
				$ele.addEventListener(
					"touchmove",
					function(event) {
						// 偏移距离
						const offsetX = event.targetTouches[0].pageX - startX;
						const offsetY = event.targetTouches[0].pageY - startY;

						$ele.style.top = `${top + offsetY}px`;
						$ele.style.left = `${left + offsetX}px`;
						$ele.style.right = "auto";
						$ele.style.bottom = "auto";

						event.preventDefault();
					},
					false
				);

				function touchDone(event) {
					const dx = event.changedTouches[0].pageX - startX;
					const dy = event.changedTouches[0].pageY - startY;

					const ty = top + dy;
					const tx = left + dx;

					$ele.style.top = `${ty}px`;
					$ele.style.left = `${tx}px`;
					$ele.style.right = "auto";
					$ele.style.bottom = "auto";

					const adsorb_safe_x = cw - width - adsorb.x;
					const adsorb_safe_y = ch - height - adsorb.y;
					
					//位置移动修复，伴随动画效果
					raf(() => {
						let nx;
						let ny = ty;

						if (!center) {
							// 效果1:进行了边界回弹，比如目前我这边只允许停靠在左右两侧 20px 的位置，不能在中间
							if (tx + width / 2 < cw / 2) {
								nx = adsorb.x;
							} else {
								nx = adsorb_safe_x;
							}
						} else {
							//效果2:进行了边界保护，自由移动	
							if (tx < adsorb.x) {
								nx = adsorb.x;
							} else if (tx > adsorb_safe_x) {
								nx = adsorb_safe_x;
							}
						}

						if (ty < adsorb.y) {
							ny = adsorb.y;
						} else if (ty > adsorb_safe_y) {
							ny = adsorb_safe_y;
						}
						console.log('dx', dx, 'dy', dy, 'tx', tx, 'nx', nx, 'ty', ty, 'ny', ny)
						// 无需移动，并且认为进行了一次点击
						// if (Math.abs(tx - nx) < 5 && Math.abs(ty - ny) < 5) {
						// 使用dx和dy判断是否移动
						if (Math.abs(dx) < 5 && Math.abs(dy) < 5) {
							$ele.click();
							return;
						}

						const MOVE_ANIM_INTER = isAndroid ?
							Math.abs(((250 * (tx - nx)) / 800) | 0) :
							200;

						$ele.style.webkitTransition =
							`left ${MOVE_ANIM_INTER}ms ease-in-out, top ${MOVE_ANIM_INTER}ms ease-in-out`;
						$ele.style.transition =
							`left ${MOVE_ANIM_INTER}ms ease-in-out, top ${MOVE_ANIM_INTER}ms ease-in-out`;

						const onAnimationDone = () => {
							$ele.style.webkitTransition = $ele.style.transition = "none";

							$ele.removeEventListener("webkitTransitionEnd", onAnimationDone, false);
							$ele.removeEventListener("transitionend", onAnimationDone, false);
						};

						$ele.addEventListener("webkitTransitionEnd", onAnimationDone, false);
						$ele.addEventListener("transitionend", onAnimationDone, false);

						$ele.style.top = `${ny}px`;
						$ele.style.left = `${nx}px`;
					});
				}

				$ele.addEventListener("touchend", touchDone, true);
				$ele.addEventListener("touchcancel", touchDone, true);
			}

		},

	}
</script>

<style>
	.draggable {
		position: fixed;
		bottom: 20px;
		right: 20px;
		background-color: gold;
		width: 40px;
		height: 60px;
	}

	.draggable2 {
		position: fixed;
		top: 20px;
		right: 20px;
		background-color: pink;
		width: 40px;
		height: 60px;
	}
</style>
